//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_IMAGE_RASTERIZER_CSTROKER_H_
#define _BUOLA_IMAGE_RASTERIZER_CSTROKER_H_

#include <buola/image/cpath.h>
#include <buola/mat/transformations.h>

///\todo don't use interpretflat... use the regular interpret and process curves especially... to not have the artifacts
/// that appear in the first arc example

namespace buola { namespace img {

struct CStrokeFace
{
    CPoint_d mCCW;
    CPoint_d mPoint;
    CPoint_d mCW;

    CSlope_d mDevSlope;
    CSlope_d mDevVector;
    CSlope_d mUsrVector;
    double mLength;
};

template<typename tRasterizer>
class CStroker
{
public:
    CStroker(const CStrokeStyle &pStyle,const mat::C2DTransform &pTrans,double pTolerance,tRasterizer &pRasterizer)
        :   mStyle(pStyle)
        ,   mTrans(pTrans)
        ,   mTolerance(pTolerance)
        ,   mPositiveDeterminant(determinant(pTrans.F())>=0)
        ,   mHasCurrentFace(false)
        ,   mHasFirstFace(false)
        ,   mHasInitialSubPath(false)
        ,   mRasterizer(pRasterizer)
    {}

private:
    void AddFan(const CSlope_d &pInSlope,const CSlope_d &pOutSlope,const CPoint_d &pMid,
                const CPoint_d &pIn,const CPoint_d &pOut,bool pCW,std::vector<CPoint_d> &pContour)
    {
#if 0
add_fan (struct stroker *stroker,
         const cairo_slope_t *in_vector,
         const cairo_slope_t *out_vector,
         const cairo_point_t *midpt,
         const cairo_point_t *inpt,
         const cairo_point_t *outpt,
         cairo_bool_t clockwise,
         struct stroke_contour *c)
{
    int start, stop, step, i, npoints;

    if (clockwise) {
        step  = 1;

        start = _cairo_pen_find_active_cw_vertex_index (&stroker->pen,
                                                        in_vector);
        if (_cairo_slope_compare (&stroker->pen.vertices[start].slope_cw,
                                  in_vector) < 0)
            start = range_step (start, 1, stroker->pen.num_vertices);

        stop  = _cairo_pen_find_active_cw_vertex_index (&stroker->pen,
                                                        out_vector);
        if (_cairo_slope_compare (&stroker->pen.vertices[stop].slope_ccw,
                                  out_vector) > 0)
        {
            stop = range_step (stop, -1, stroker->pen.num_vertices);
            if (_cairo_slope_compare (&stroker->pen.vertices[stop].slope_cw,
                                      in_vector) < 0)
                return;
        }

        npoints = stop - start;
    } else {
        step  = -1;

        start = _cairo_pen_find_active_ccw_vertex_index (&stroker->pen,
                                                         in_vector);
        if (_cairo_slope_compare (&stroker->pen.vertices[start].slope_ccw,
                                  in_vector) < 0)
            start = range_step (start, -1, stroker->pen.num_vertices);

        stop  = _cairo_pen_find_active_ccw_vertex_index (&stroker->pen,
                                                         out_vector);
        if (_cairo_slope_compare (&stroker->pen.vertices[stop].slope_cw,
                                  out_vector) > 0)
        {
            stop = range_step (stop, 1, stroker->pen.num_vertices);
            if (_cairo_slope_compare (&stroker->pen.vertices[stop].slope_ccw,
                                      in_vector) < 0)
                return;
        }

        npoints = start - stop;
    }
    stop = range_step (stop, step, stroker->pen.num_vertices);
    if (npoints < 0)
        npoints += stroker->pen.num_vertices;
    if (npoints <= 1)
        return;

    for (i = start;
         i != stop;
        i = range_step (i, step, stroker->pen.num_vertices))
    {
        cairo_point_t p = *midpt;
        translate_point (&p, &stroker->pen.vertices[i].point);
        contour_add_point (stroker, c, &p);
    }
#endif
    }

    void AddCap(const CStrokeFace &pFace,std::vector<CPoint_d> &pContour)
    {
        switch(mStyle.mCap)
        {
        case ELineCap::ROUND:
            {
                CSlope_d lSlope=-pFace.mDevVector;
                AddFan(pFace.mDevVector,lSlope,pFace.mPoint,pFace.mCCW,pFace.mCW,false,pContour);
                break;
            }

        case ELineCap::SQUARE:
            {
                CSlope_d lSlope=pFace.mUsrVector;

                lSlope.x*=mStyle.mWidth/2.0;
                lSlope.y*=mStyle.mWidth/2.0;

                CSlope_d lFVector(transform_vector(mTrans,lSlope));

                ///\todo check for tolerance
                pContour.emplace_back(pFace.mCCW.x+lFVector.x,pFace.mCCW.y+lFVector.y);
                pContour.emplace_back(pFace.mCW.x+lFVector.x,pFace.mCW.y+lFVector.y);
            }

        case ELineCap::BUTT:
            break;
        }

        pContour.push_back(pFace.mCW);
    }

    void AddLeadingCap(const CStrokeFace &pFace,std::vector<CPoint_d> &pContour)
    {
        CStrokeFace lReversed=pFace;

        lReversed.mUsrVector=-lReversed.mUsrVector;
        lReversed.mDevVector=-lReversed.mDevVector;

        using std::swap;
        swap(lReversed.mCW,lReversed.mCCW);

        AddCap(lReversed,pContour);
    }

    void AddTrailingCap(const CStrokeFace &pFace,std::vector<CPoint_d> &pContour)
    {
        AddCap(pFace,pContour);
    }

    void PushPolygon(const std::vector<CPoint_d> &pV)
    {
        if(pV.size()<2) return;
        mRasterizer.MoveTo(pV[0]);
        for(int i=1;i<pV.size();i++)
            mRasterizer.LineTo(pV[i]);
    }
    
    void PushPolygon2(const std::vector<CPoint_d> &pV)
    {
        if(pV.size()<2) return;
        mRasterizer.MoveTo(pV.back());
        for(int i=pV.size()-2;i>=0;i--)
            mRasterizer.LineTo(pV[i]);
    }

    void AddCaps()
    {
        if(mHasInitialSubPath&&!mHasFirstFace&&!mHasCurrentFace&&mStyle.mCap==ELineCap::ROUND)
        {
            CSlope_d lSlope(1.0,0.0);

            CStrokeFace lFace=ComputeFace(mFirstPoint,lSlope);

            AddLeadingCap(lFace,mCCW);
            AddTrailingCap(lFace,mCCW);

            mCCW.push_back(mCCW.front());
            PushPolygon(mCCW); //,-1
            mCCW.clear();
        }
        else
        {
            if(mHasCurrentFace)
                AddTrailingCap(mCurrentFace,mCCW);

            PushPolygon(mCCW); //,-1
            mCCW.clear();

            if(mHasFirstFace)
            {
                mCCW.push_back(mFirstFace.mCW);
                AddLeadingCap(mFirstFace,mCCW);

                PushPolygon(mCCW); //,-1
                mCCW.clear();
            }

            PushPolygon2(mCW); //,1
            mCW.clear();
        }
    }

    CStrokeFace ComputeFace(const CPoint_d &pP,const CSlope_d &pSlope)
    {
        CStrokeFace lFace;

        CPoint_d lFaceSlope;
        CPoint_d lOffsetCCW,lOffsetCW;

        CSlope_d lSlope(pSlope);
        lFace.mLength=lSlope.Normalize();
        lFace.mDevSlope=lSlope;

        if(!mTrans.IsIdentity())
        {
            lSlope=transform_vector(mTrans.I(),lSlope);
            lSlope.Normalize();

            if(mPositiveDeterminant)
            {
                lFaceSlope.x=-lSlope.y*mStyle.mWidth/2.0;
                lFaceSlope.y=lSlope.x*mStyle.mWidth/2.0;
            }
            else
            {
                lFaceSlope.x=lSlope.y*mStyle.mWidth/2.0;
                lFaceSlope.y=-lSlope.x*mStyle.mWidth/2.0;
            }
        }
        else
        {
            lFaceSlope.x=-lSlope.y*mStyle.mWidth/2.0;
            lFaceSlope.y=lSlope.x*mStyle.mWidth/2.0;
        }

        lOffsetCCW=CPoint_d(lFaceSlope);
        lOffsetCW=-lOffsetCCW;

        lFace.mPoint=pP;
        lFace.mCCW=pP+lOffsetCCW;
        lFace.mCW=pP+lOffsetCW;

        lFace.mUsrVector=lSlope;
        lFace.mDevVector=pSlope;

        return lFace;
    }

    void InnerJoin(const CStrokeFace &pIn,const CStrokeFace &pOut,bool pClockwise)
    {
        if(pClockwise)
        {
            mCCW.push_back(pIn.mPoint);
            mCCW.push_back(pOut.mCCW);
        }
        else
        {
            mCW.push_back(pIn.mPoint);
            mCW.push_back(pOut.mCW);
        }
    }

    void OuterJoin(const CStrokeFace &pIn,const CStrokeFace &pOut,bool pClockwise)
    {
        CPoint_d lIn,lOut;
        if(pIn.mCW==pOut.mCW&&pIn.mCCW==pOut.mCCW) return;

        std::vector<CPoint_d> *lOuter;

        if(pClockwise)
        {
            lIn=pIn.mCW;
            lOut=pOut.mCW;
            lOuter=&mCW;
        }
        else
        {
            lIn=pIn.mCCW;
            lOut=pOut.mCCW;
            lOuter=&mCCW;
        }

        switch(mStyle.mJoin)
        {
        case ELineJoin::ROUND:
            AddFan(pIn.mDevVector,pOut.mDevVector,pIn.mPoint,lIn,lOut,pClockwise,*lOuter);
            break;

        case ELineJoin::MITER:
            {
                double lInDotOut=-pIn.mUsrVector.x*pOut.mUsrVector.x-pIn.mUsrVector.y-pOut.mUsrVector.y;
                double lML=5; //mStyle.mMiterLimit

                if(2<=lML*lML*(1-lInDotOut))
                {
                    double                x1, y1, x2, y2;
                    double                mx, my;
                    double                dx1, dx2, dy1, dy2;
//                    double                ix, iy;
//                    double                fdx1, fdy1, fdx2, fdy2;
//                    double                mdx, mdy;

                    /*
                    * we've got the points already transformed to device
                    * space, but need to do some computation with them and
                    * also need to transform the slope from user space to
                    * device space
                    */
                    /* outer point of incoming line face */
                    x1 = lIn.x;
                    y1 = lIn.y;
                    dx1 = pIn.mUsrVector.x;
                    dy1 = pIn.mUsrVector.y;
                    ///\todo cairo_matrix_transform_distance (stroker->ctm, &dx1, &dy1);

                    /* outer point of outgoing line face */
                    x2 = lOut.x;
                    y2 = lOut.y;
                    dx2 = pOut.mUsrVector.x;
                    dy2 = pOut.mUsrVector.y;
                    ///\todo cairo_matrix_transform_distance (stroker->ctm, &dx2, &dy2);

                    /*
                    * Compute the location of the outer corner of the miter.
                    * That's pretty easy -- just the intersection of the two
                    * outer edges.  We've got slopes and points on each
                    * of those edges.  Compute my directly, then compute
                    * mx by using the edge with the larger dy; that avoids
                    * dividing by values close to zero.
                    */
                    my = (((x2 - x1) * dy1 * dy2 - y2 * dx2 * dy1 + y1 * dx1 * dy2) /
                            (dx1 * dy2 - dx2 * dy1));
                    if (fabs (dy1) >= fabs (dy2))
                        mx = (my - y1) * dx1 / dy1 + x1;
                    else
                        mx = (my - y2) * dx2 / dy2 + x2;

                    /*
                    * When the two outer edges are nearly parallel, slight
                    * perturbations in the position of the outer points of the lines
                    * caused by representing them in fixed point form can cause the
                    * intersection point of the miter to move a large amount. If
                    * that moves the miter intersection from between the two faces,
                    * then draw a bevel instead.
                    */

//                    ix = pIn.mPoint.x;
//                    iy = pIn.mPoint.y;

                    /* slope of one face */
//                    fdx1 = x1 - ix; fdy1 = y1 - iy;

                    /* slope of the other face */
//                    fdx2 = x2 - ix; fdy2 = y2 - iy;

                    /* slope from the intersection to the miter point */
//                    mdx = mx - ix; mdy = my - iy;

                    /*
                    * Make sure the miter point line lies between the two
                    * faces by comparing the slopes
                    */
///\todo
//                    if (slope_compare_sgn (fdx1, fdy1, mdx, mdy) !=
//                        slope_compare_sgn (fdx2, fdy2, mdx, mdy))
                    {
                        lOuter->back()={mx,my};
                        return;
                    }
                    
                }
                break;
            }
        case ELineJoin::BEVEL:
            break;
        }

        lOuter->push_back(lOut);
    }

public:
    void MoveTo(const CPoint_d &pP)
    {
        AddCaps();

        mHasCurrentFace=false;
        mHasFirstFace=false;
        mHasInitialSubPath=false;

        mFirstPoint=pP;

        mCurrentFace.mPoint=pP;
    }

    void LineTo(const CPoint_d &pP)
    {
        mHasInitialSubPath=true;

        if(mCurrentFace.mPoint==pP) return;

        CSlope_d lDevSlope(mCurrentFace.mPoint,pP);

        CStrokeFace lStart=ComputeFace(mCurrentFace.mPoint,lDevSlope);

        bool lMoveLast=false;

        if(mHasCurrentFace)
        {
            if(mCurrentFace.mDevVector==lStart.mDevVector)
            {
                lMoveLast=true;
            }
            else
            {
                bool lClockwise=mCurrentFace.mDevVector<lStart.mDevVector;

                ///\todo check whether it is within tolerance
                OuterJoin(mCurrentFace,lStart,lClockwise);
                InnerJoin(mCurrentFace,lStart,lClockwise);
            }
        }
        else
        {
            if(!mHasFirstFace)
            {
                mFirstFace=lStart;
                mHasFirstFace=true;
            }

            mHasCurrentFace=true;

            ///\todo do the tolerance check
            mCW.push_back(lStart.mCW);
            mCCW.push_back(lStart.mCCW);
        }

        mCurrentFace=lStart;

        mCurrentFace.mPoint=pP;
        mCurrentFace.mCCW+={lDevSlope.x,lDevSlope.y};
        mCurrentFace.mCW+={lDevSlope.x,lDevSlope.y};

        if(lMoveLast)
        {
            mCW.back()=mCurrentFace.mCW;
            mCCW.back()=mCurrentFace.mCCW;
        }
        else
        {
            mCW.push_back(mCurrentFace.mCW);
            mCCW.push_back(mCurrentFace.mCCW);
        }
    }

    void Stroke(const CPath &pPath)
    {
        pPath.InterpretFlat(*this);
        AddCaps();
    }

private:
    const CStrokeStyle &mStyle;
    const mat::C2DTransform &mTrans;
    double mTolerance;
    bool mPositiveDeterminant;
    bool mHasCurrentFace;
    bool mHasFirstFace;
    bool mHasInitialSubPath;
    tRasterizer &mRasterizer;

    CPoint_d mFirstPoint;
    CStrokeFace mCurrentFace;
    CStrokeFace mFirstFace;

    std::vector<CPoint_d> mCW;
    std::vector<CPoint_d> mCCW;
};

/*namespace img*/ } /*namespace buola*/ }

#endif
